```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MyBatis SQL执行与映射机制解析</title>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            color: #333;
            background-color: #f9fafb;
            line-height: 1.6;
        }
        
        .hero {
            background: linear-gradient(135deg, #4361ee 0%, #3a0ca3 100%);
            color: white;
        }
        
        .code-block {
            background-color: #2d3748;
            border-radius: 0.5rem;
            padding: 1.5rem;
            position: relative;
        }
        
        .code-block pre {
            color: #e2e8f0;
            font-family: monospace;
            overflow-x: auto;
        }
        
        .code-block .copy-btn {
            position: absolute;
            top: 0.5rem;
            right: 0.5rem;
            background-color: rgba(255,255,255,0.1);
            color: white;
            border: none;
            border-radius: 0.25rem;
            padding: 0.25rem 0.5rem;
            cursor: pointer;
        }
        
        .code-block .copy-btn:hover {
            background-color: rgba(255,255,255,0.2);
        }
        
        .section {
            background-color: white;
            border-radius: 0.75rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
            transition: all 0.3s ease;
        }
        
        .section:hover {
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
            transform: translateY(-2px);
        }
        
        .highlight {
            position: relative;
        }
        
        .highlight::before {
            content: '';
            position: absolute;
            left: -0.5rem;
            top: 0;
            height: 100%;
            width: 4px;
            background-color: #4361ee;
            border-radius: 2px;
        }
        
        .footer {
            background-color: #1a202c;
            color: #e2e8f0;
        }
        
        .footer a:hover {
            color: #63b3ed;
        }
        
        .first-letter {
            font-size: 3.5rem;
            line-height: 1;
            float: left;
            margin-right: 0.5rem;
            margin-bottom: -0.5rem;
            color: #3a0ca3;
            font-weight: bold;
        }
        
        .mermaid {
            background-color: white;
            padding: 1.5rem;
            border-radius: 0.75rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
            margin: 2rem 0;
        }
    </style>
</head>
<body class="min-h-screen flex flex-col">
    <header class="hero py-20 px-4 sm:px-6 lg:px-8">
        <div class="max-w-5xl mx-auto text-center">
            <h1 class="text-4xl md:text-5xl font-bold mb-4 font-serif">MyBatis SQL执行与映射机制</h1>
            <p class="text-xl md:text-2xl opacity-90 mb-8 font-serif">深入解析MyBatis核心工作原理</p>
            <div class="inline-flex items-center space-x-2 bg-white text-blue-900 px-4 py-2 rounded-full font-medium">
                <i class="fas fa-code"></i>
                <span>技术解析</span>
            </div>
        </div>
    </header>

    <main class="flex-grow container mx-auto px-4 sm:px-6 lg:px-8 py-12 max-w-5xl">
        <div class="mb-12">
            <p class="text-lg text-gray-700 leading-relaxed">
                <span class="first-letter">在</span>MyBatis框架中，SQL执行与映射处理是核心机制之一。SQL执行涉及到SQL语句的解析、执行、结果映射等多个步骤，而映射处理则涉及到将数据库查询结果转换成Java对象的过程。本文将深入剖析这一过程的实现原理，帮助开发者更好地理解和使用MyBatis。
            </p>
        </div>

        <div class="grid gap-12">
            <section id="sql-entry" class="section p-8">
                <h2 class="text-2xl font-bold mb-6 text-blue-900 flex items-center">
                    <span class="w-8 h-8 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center mr-4">1</span>
                    SQL执行的入口与流程
                </h2>
                <div class="space-y-6">
                    <p class="text-gray-700 leading-relaxed">
                        MyBatis的SQL执行过程通过<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">SqlSession</code>接口提供，最常用的方法是<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">selectOne</code>、<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">selectList</code>、<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">insert</code>、<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">update</code>、<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">delete</code>等，这些方法封装了SQL的执行。它们内部最终会调用<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">Executor</code>来执行SQL。
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>public interface SqlSession {
    &lt;T&gt; T selectOne(String statement, Object parameter);
    &lt;E&gt; List&lt;E&gt; selectList(String statement, Object parameter);
    int insert(String statement, Object parameter);
    int update(String statement, Object parameter);
    int delete(String statement, Object parameter);
}</code></pre>
                    </div>
                    
                    <div class="bg-blue-50 p-4 rounded-lg">
                        <div class="flex">
                            <div class="flex-shrink-0 text-blue-500">
                                <i class="fas fa-info-circle"></i>
                            </div>
                            <div class="ml-3">
                                <p class="text-blue-700">
                                    这些方法会传递SQL的标识符（<code>statement</code>）和执行参数（<code>parameter</code>）给<code>Executor</code>，并由<code>Executor</code>执行实际的数据库操作。
                                </p>
                            </div>
                        </div>
                    </div>
                </div>
            </section>

            <section id="executor" class="section p-8">
                <h2 class="text-2xl font-bold mb-6 text-blue-900 flex items-center">
                    <span class="w-8 h-8 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center mr-4">2</span>
                    Executor的作用与执行流程
                </h2>
                <div class="space-y-6">
                    <p class="text-gray-700 leading-relaxed">
                        <code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">Executor</code>是MyBatis中负责执行SQL的核心接口，主要实现了SQL的执行、缓存管理、批量操作等功能。<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">Executor</code>有多个实现类，其中最常用的是<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">SimpleExecutor</code>和<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">ReuseExecutor</code>。
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>public interface Executor {
    &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameter, 
                     RowBounds rowBounds, ResultHandler resultHandler);
    int update(MappedStatement ms, Object parameter);
    void commit(boolean required);
    void rollback(boolean required);
}</code></pre>
                    </div>
                    
                    <ul class="list-disc pl-6 space-y-2 text-gray-700">
                        <li><code>query</code>方法用于执行查询操作，<code>update</code>方法用于执行更新、删除和插入操作。</li>
                        <li><code>Executor</code>的实现会通过<code>MappedStatement</code>来获取SQL语句，并执行SQL。</li>
                    </ul>
                    
                    <p class="text-gray-700 leading-relaxed">
                        <code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">SimpleExecutor</code>的部分实现如下：
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>public class SimpleExecutor implements Executor {
    @Override
    public &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameter, 
                            RowBounds rowBounds, ResultHandler resultHandler) {
        // 创建Statement对象并执行SQL
        Statement stmt = prepareStatement(ms, parameter, rowBounds);
        return queryFromDatabase(stmt, resultHandler);
    }
}</code></pre>
                    </div>
                    
                    <div class="bg-blue-50 p-4 rounded-lg">
                        <div class="flex">
                            <div class="flex-shrink-0 text-blue-500">
                                <i class="fas fa-lightbulb"></i>
                            </div>
                            <div class="ml-3">
                                <p class="text-blue-700">
                                    在<code>SimpleExecutor.query()</code>中，会调用<code>prepareStatement</code>方法来创建SQL语句，并执行查询。SQL的具体实现交给JDBC。
                                </p>
                            </div>
                        </div>
                    </div>
                </div>
            </section>

            <section id="mapped-statement" class="section p-8">
                <h2 class="text-2xl font-bold mb-6 text-blue-900 flex items-center">
                    <span class="w-8 h-8 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center mr-4">3</span>
                    MappedStatement的作用与获取
                </h2>
                <div class="space-y-6">
                    <p class="text-gray-700 leading-relaxed">
                        <code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">MappedStatement</code>是MyBatis中的一个关键对象，代表了一个映射的SQL语句，包含了SQL语句的详细信息、参数类型、返回类型等。
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>public class MappedStatement {
    private final String id;
    private final SqlCommandType sqlCommandType;
    private final BoundSql boundSql;
    
    public MappedStatement(String id, SqlCommandType sqlCommandType, BoundSql boundSql) {
        this.id = id;
        this.sqlCommandType = sqlCommandType;
        this.boundSql = boundSql;
    }

    public BoundSql getBoundSql(Object parameter) {
        // 返回BoundSql对象，包含SQL和参数
        return boundSql;
    }
}</code></pre>
                    </div>
                    
                    <ul class="list-disc pl-6 space-y-2 text-gray-700">
                        <li><code>MappedStatement</code>会根据方法名、SQL命令类型和<code>BoundSql</code>构造对象。<code>BoundSql</code>封装了SQL语句和对应的参数。</li>
                    </ul>
                    
                    <p class="text-gray-700 leading-relaxed">
                        在<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">Executor</code>中，<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">MappedStatement</code>通过<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">SqlSession.getConfiguration().getMappedStatement()</code>方法获取：
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>MappedStatement mappedStatement = sqlSession.getConfiguration().getMappedStatement(statement);</code></pre>
                    </div>
                    
                    <div class="bg-blue-50 p-4 rounded-lg">
                        <div class="flex">
                            <div class="flex-shrink-0 text-blue-500">
                                <i class="fas fa-info-circle"></i>
                            </div>
                            <div class="ml-3">
                                <p class="text-blue-700">
                                    <code>statement</code>是SQL的标识符，对应XML中的<code>&lt;select&gt;</code>, <code>&lt;insert&gt;</code>, <code>&lt;update&gt;</code>, <code>&lt;delete&gt;</code>等标签。
                                </p>
                            </div>
                        </div>
                    </div>
                </div>
            </section>

            <section id="bound-sql" class="section p-8">
                <h2 class="text-2xl font-bold mb-6 text-blue-900 flex items-center">
                    <span class="w-8 h-8 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center mr-4">4</span>
                    BoundSql与参数绑定
                </h2>
                <div class="space-y-6">
                    <p class="text-gray-700 leading-relaxed">
                        <code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">BoundSql</code>封装了SQL语句和对应的参数，它是SQL执行过程中的重要组成部分。它负责将方法参数和SQL语句的占位符进行绑定。
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>public class BoundSql {
    private final String sql;
    private final List&lt;ParameterMapping&gt; parameterMappings;

    public BoundSql(String sql, List&lt;ParameterMapping&gt; parameterMappings) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
    }

    public String getSql() {
        return sql;
    }

    public List&lt;ParameterMapping&gt; getParameterMappings() {
        return parameterMappings;
    }
}</code></pre>
                    </div>
                    
                    <ul class="list-disc pl-6 space-y-2 text-gray-700">
                        <li><code>BoundSql</code>保存了SQL和与之相关的参数映射，<code>parameterMappings</code>是SQL中占位符和参数之间的映射关系。</li>
                    </ul>
                    
                    <p class="text-gray-700 leading-relaxed">
                        在<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">Executor.query()</code>方法中，<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">BoundSql</code>的<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">sql</code>会通过JDBC的<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">PreparedStatement</code>来执行：
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>PreparedStatement pstmt = connection.prepareStatement(boundSql.getSql());
// 设置SQL参数
for (int i = 0; i &lt; boundSql.getParameterMappings().size(); i++) {
    pstmt.setObject(i + 1, parameter[i]);
}</code></pre>
                    </div>
                    
                    <div class="bg-blue-50 p-4 rounded-lg">
                        <div class="flex">
                            <div class="flex-shrink-0 text-blue-500">
                                <i class="fas fa-lightbulb"></i>
                            </div>
                            <div class="ml-3">
                                <p class="text-blue-700">
                                    <code>BoundSql</code>中的<code>parameterMappings</code>会根据方法参数填充到SQL语句中。
                                </p>
                            </div>
                        </div>
                    </div>
                </div>
            </section>

            <section id="result-set-handler" class="section p-8">
                <h2 class="text-2xl font-bold mb-6 text-blue-900 flex items-center">
                    <span class="w-8 h-8 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center mr-4">5</span>
                    ResultSetHandler与结果映射
                </h2>
                <div class="space-y-6">
                    <p class="text-gray-700 leading-relaxed">
                        <code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">ResultSetHandler</code>是MyBatis用于处理SQL查询结果的接口，它负责将数据库查询结果映射为Java对象。MyBatis支持多种结果映射方式，包括基本类型、POJO对象、List、Map等。
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>public interface ResultSetHandler {
    &lt;E&gt; List&lt;E&gt; handleResultSets(Statement stmt) throws SQLException;
}</code></pre>
                    </div>
                    
                    <ul class="list-disc pl-6 space-y-2 text-gray-700">
                        <li><code>handleResultSets()</code>方法用于将<code>ResultSet</code>中的数据映射到Java对象。MyBatis会根据<code>ResultMap</code>来决定如何将<code>ResultSet</code>的列映射到Java对象的属性上。</li>
                    </ul>
                    
                    <p class="text-gray-700 leading-relaxed">
                        在查询过程中，<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">Executor.query()</code>会通过<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">ResultSetHandler</code>来处理查询结果：
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>public &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameter, 
                        RowBounds rowBounds, ResultHandler resultHandler) {
    Statement stmt = prepareStatement(ms, parameter, rowBounds);
    return resultHandler.handleResultSets(stmt);  // 结果映射
}</code></pre>
                    </div>
                    
                    <div class="bg-blue-50 p-4 rounded-lg">
                        <div class="flex">
                            <div class="flex-shrink-0 text-blue-500">
                                <i class="fas fa-info-circle"></i>
                            </div>
                            <div class="ml-3">
                                <p class="text-blue-700">
                                    通过<code>ResultSetHandler.handleResultSets()</code>，查询结果会根据<code>ResultMap</code>的定义转换为Java对象。
                                </p>
                            </div>
                        </div>
                    </div>
                </div>
            </section>

            <section id="result-map" class="section p-8">
                <h2 class="text-2xl font-bold mb-6 text-blue-900 flex items-center">
                    <span class="w-8 h-8 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center mr-4">6</span>
                    ResultMap与动态映射
                </h2>
                <div class="space-y-6">
                    <p class="text-gray-700 leading-relaxed">
                        <code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">ResultMap</code>是MyBatis的一个核心概念，它定义了如何将数据库查询结果映射为Java对象的字段。<code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">ResultMap</code>根据SQL查询结果中的列名和Java对象的属性进行映射。
                    </p>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>&lt;resultMap id="userResultMap" type="User"&gt;
    &lt;id property="id" column="user_id"/&gt;
    &lt;result property="username" column="user_name"/&gt;
&lt;/resultMap&gt;</code></pre>
                    </div>
                    
                    <ul class="list-disc pl-6 space-y-2 text-gray-700">
                        <li>在<code>ResultSetHandler</code>处理结果时，MyBatis会根据<code>ResultMap</code>的映射关系将<code>ResultSet</code>的每一列与Java对象的属性进行绑定。</li>
                    </ul>
                    
                    <p class="text-gray-700 leading-relaxed">
                        <code class="bg-blue-100 text-blue-800 px-2 py-1 rounded">ResultMap</code>的映射过程如下：
                    </p>
                    
                    <ol class="list-decimal pl-6 space-y-2 text-gray-700">
                        <li>获取<code>ResultMap</code>中的字段映射。</li>
                        <li>根据<code>ResultSet</code>中的列名查找对应的字段。</li>
                        <li>将<code>ResultSet</code>中的值赋给Java对象的属性。</li>
                    </ol>
                    
                    <div class="code-block">
                        <button class="copy-btn" onclick="copyCode(this)">
                            <i class="far fa-copy"></i>
                        </button>
                        <pre><code>public class DefaultResultSetHandler implements ResultSetHandler {
    @Override
    public &lt;E&gt; List&lt;E&gt; handleResultSets(Statement stmt) throws SQLException {
        ResultSet rs = stmt.getResultSet();
        List&lt;E&gt; resultList = new ArrayList&lt;&gt;();
        while (rs.next()) {
            E result = (E) mapRow(rs);  // 映射每一行数据
            resultList.add(result);
        }
        return resultList;
    }

    private &lt;E&gt; E mapRow(ResultSet rs) throws SQLException {
        // 根据ResultMap将ResultSet的列映射到Java对象
        return (E) resultMap.getConstructor().newInstance();
    }
}</code></pre>
                    </div>
                    
                    <div class="bg-blue-50 p-4 rounded-lg">
                        <div class="flex">
                            <div class="flex-shrink-0 text-blue-500">
                                <i class="fas fa-lightbulb"></i>
                            </div>
                            <div class="ml-3">
                                <p class="text-blue-700">
                                    在<code>handleResultSets()</code>中，<code>mapRow()</code>方法会根据<code>ResultMap</code>将每一行的<code>ResultSet</code>数据映射为Java对象。
                                </p>
                            </div>
                        </div>
                    </div>
                </div>
            </section>

            <section id="execution-flow" class="section p-8">
                <h2 class="text-2xl font-bold mb-6 text-blue-900 flex items-center">
                    <span class="w-8 h-8 bg-blue-100 text-blue-800 rounded-full flex items-center justify-center mr-4">7</span>
                    SQL执行与映射的执行流程
                </h2>
                <div class="space-y-6">
                    <p class="text-gray-700 leading-relaxed">
                        通过上面的源码分析，MyBatis的SQL执行和映射处理流程如下：
                    </p>
                    
                    <div class="mermaid">
                        graph TD
                            A[获取Mapper方法] --> B[构建BoundSql]
                            B --> C[执行SQL]
                            C --> D[结果映射]
                            D --> E[返回结果]
                            
                            style A fill:#EBF5FB,stroke:#3498DB
                            style B fill:#EBF5FB,stroke:#3498DB
                            style C fill:#EBF5FB,stroke:#3498DB
                            style D fill:#EBF5FB,stroke:#3498DB
                            style E fill:#EBF5FB,stroke:#3498DB
                    </div>
                    
                    <ol class="list-decimal pl-6 space-y-2 text-gray-700">
                        <li class="highlight pl-3"><strong>获取Mapper方法</strong>：当调用<code>SqlSession.selectOne()</code>等方法时，MyBatis首先通过<code>MappedStatement</code>获取SQL语句和相关配置。</li>
                        <li class="highlight pl-3"><strong>构建BoundSql</strong>：根据SQL和参数，MyBatis构建<code>BoundSql</code>对象，将参数绑定到SQL语句的占位符中。</li>
                        <li class="highlight pl-3"><strong>执行SQL</strong>：通过<code>Executor</code>执行SQL，JDBC的<code>PreparedStatement</code>将SQL语句与参数一起发送到数据库执行。</li>
                        <li class="highlight pl-3"><strong>结果映射</strong>：数据库查询结果返回后，<code>ResultSetHandler</code>根据<code>ResultMap</code>将<code>ResultSet</code>的列映射为Java对象。</li>
                        <li class="highlight pl-3"><strong>返回结果</strong>：最终将映射后的Java对象返回给调用者。</li>
                    </ol>
                </div>
            </section>
        </div>
    </main>

    <footer class="footer py-8 px-4 sm:px-6 lg:px-8">
        <div class="max-w-5xl mx-auto flex flex-col sm:flex-row justify-between items-center">
            <div class="mb-4 sm:mb-0">
                <p class="text-sm">技术小馆</p>
            </div>
            <div>
                <a href="http://www.yuque.com/jtostring" class="text-sm hover:text-blue-300 transition-colors">
                    <i class="fas fa-globe mr-1"></i> http://www.yuque.com/jtostring
                </a>
            </div>
        </div>
    </footer>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            }
        });
        
        function copyCode(button) {
            const codeBlock = button.nextElementSibling;
            const range = document.createRange();
            range.selectNode(codeBlock);
            window.getSelection().removeAllRanges();
            window.getSelection().addRange(range);
            
            try {
                document.execCommand('copy');
                const originalText = button.innerHTML;
                button.innerHTML = '<i class="fas fa-check"></i>';
                setTimeout(() => {
                    button.innerHTML = originalText;
                }, 2000);
            } catch(err) {
                console.error('复制失败:', err);
            }
            
            window.getSelection().removeAllRanges();
        }
    </script>
</body>
</html>
```